//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <core.h>
#include <_hal.h>
#include <init.h>
#include "setup.h"

#define __PHYSADDR(va, physbase) \
    ((physaddr_t)(va) - (offset_t)KERNEL_BASE + (physbase))

#define __VIRTADDR(pa, physbase) \
    ((virtaddr_t)((pa) - (physbase) + (offset_t)KERNEL_BASE))

DECL_SECTION(".initpagedir")
        PageDirEntry g_initPageDir[c_uNumberOfPageDirEntries];

DECL_SECTION(".tmppagetabs")
        PageTabEntry g_pageTab0[c_uNumberOfPageTabEntries];

DECL_SECTION(".tmppagetabs")
        PageTabEntry g_pageTab1[c_uNumberOfPageTabEntries];

DECL_SECTION(".tmppagetabs")
        PageTabEntry g_pageTab2[c_uNumberOfPageTabEntries];

EXTERN DECL_CODEINIT void CDECL SetupPaging(
    physaddr_t paKernelPhysBase, physaddr_t paKernelImagePhysBase)
{
    // Setup init page directory
    PageDirEntry *pPageDir =
        (PageDirEntry *)(virtaddr_t)
            __PHYSADDR(g_initPageDir, paKernelPhysBase);

    memset(pPageDir, 0, c_uSizeOfPageDir);

    uint_t i;
    PageTabEntry *pPageTab0 =
        (PageTabEntry *)(virtaddr_t)__PHYSADDR(g_pageTab0, paKernelPhysBase);

    for (i = 0; i < c_uNumberOfPageTabEntries; i++) {
        pPageTab0[i].uValue =
            PAGENO_TO_PHYSADDR(i) + PageEntryFlag_Present + PageEntryFlag_RW;
    }

    pPageDir[PHYSADDR_TO_PAGETABNO(paKernelImagePhysBase)].uValue =
    pPageDir[PHYSADDR_TO_PAGETABNO(
            (physaddr_t)VIRTADDR(paKernelImagePhysBase))].uValue =
            (physaddr_t)pPageTab0 + PageEntryFlag_Present + PageEntryFlag_RW;

    PageTabEntry *pPageTab1 =
        (PageTabEntry *)(virtaddr_t)__PHYSADDR(g_pageTab1, paKernelPhysBase);

    for (i = 0; i < c_uNumberOfPageTabEntries; i++) {
        pPageTab1[i].uValue =
            PAGEDIR_SIZE + PAGENO_TO_PHYSADDR(i)
                + PageEntryFlag_Present + PageEntryFlag_RW;
    }

    pPageDir[PHYSADDR_TO_PAGETABNO(paKernelImagePhysBase) + 1].uValue =
    pPageDir[PHYSADDR_TO_PAGETABNO(
            (physaddr_t)VIRTADDR(paKernelImagePhysBase)) + 1].uValue =
            (physaddr_t)pPageTab1 + PageEntryFlag_Present + PageEntryFlag_RW;

    SetPageDirAddress((physaddr_t)pPageDir);

    EnablePaging(FALSE);

    FlushAllTlbs();

    // Update eip
    ASM("push   $__SetupPaging_Next;"
        "ret;"
        "__SetupPaging_Next:");

    // Update esp and ebp
    ASM("addl   %0, %%esp;"
        "addl   %0, %%ebp;"
        : :"g"(__VIRTADDR(0, paKernelPhysBase)));

    // Update stack frames
    uint_t *ebp;
    ASM("movl   %%ebp, %0;"
        :"=m"(ebp));

    while (TRUE) {
        ebp[1] = (uint_t)VIRTADDR(ebp[1]);      // Update return address

        if (0 == ebp[0]) break;

        ebp[0] = (uint_t)VIRTADDR(ebp[0]);      // Update frame pointer
        ebp    = (uint_t *)ebp[0];
    }

    // Remove page table 0 and 1 mapping if it's redundant.
    g_initPageDir[PHYSADDR_TO_PAGETABNO(paKernelImagePhysBase)].uValue = 0;
    g_initPageDir[PHYSADDR_TO_PAGETABNO(paKernelImagePhysBase) + 1].uValue = 0;

    FlushAllTlbs();
}

EXTERN DECL_CODEINIT bool_t CDECL InitKernelMapping(
    uint_t uBasePageNo, uint_t uLimitPageNo)
{
    assert(IS_VALID_PAGENO(uLimitPageNo - 1));
    assert(uBasePageNo < uLimitPageNo);

    PageDirEntry pde0 = g_initPageDir[0];

    for (uint_t uPageNo = uBasePageNo; uPageNo < uLimitPageNo;) {
        PageTabEntry *pPageTable = AllocKernelPageTable();
        if (NULL == pPageTable) return FALSE;

        physaddr_t paPageTable          = PHYSADDR(pPageTable);
        PageTabEntry *pMappedPageTable  = (PageTabEntry *)(virtaddr_t)0;

        g_pageTab2[0].uValue =
            paPageTable + PageEntryFlag_Present + PageEntryFlag_RW;
        g_initPageDir[0].uValue =
            PHYSADDR(g_pageTab2) + PageEntryFlag_Present + PageEntryFlag_RW;
        FlushTlb(pMappedPageTable);

        memset(pMappedPageTable, 0, c_uSizeOfPageTab);

        uint_t uBasePageNoOfPageTable =
            RoundDown2(uPageNo, c_uNumberOfPageTabEntries);

        for (uint_t i = uPageNo % c_uNumberOfPageTabEntries;
            i < Min(c_uNumberOfPageTabEntries,
                    uLimitPageNo - uBasePageNoOfPageTable);
            i++, uPageNo++) {
            pMappedPageTable[i].uValue =
                PAGENO_TO_PHYSADDR(VIRTPAGENO_TO_PHYSPAGENO(
                    uBasePageNoOfPageTable + i))
                + PageEntryFlag_Present + PageEntryFlag_RW;
        }

        g_initPageDir[PAGENO_TO_PAGETABNO(uBasePageNoOfPageTable)].uValue =
            paPageTable + PageEntryFlag_Present + PageEntryFlag_RW;
    }

    g_initPageDir[0] = pde0;

    FlushAllTlbs();

    return TRUE;
}
